package com.immer.session;

import com.immer.session.request.RequestEventSubject;
import com.immer.session.exception.RedisSessionException;
import org.springframework.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Set;

/**
 *  session 管理器
 *  @Author
 */
public class RedisSessionManger {
    /**
     *  cookie session联通
     */
    private static final String  SESSION_ID_COOKIE = "JSESSIONID";

    /**
     *  如果session没有变化，则5分钟更新一次redis
     */
    private static final int expirationUpdateInterval = 5 * 60;
    /**
     *  session 存活时间
     */
    private static final int maxInactiveInterval = 30 * 60;

    private RedisSessionClient sessionClient;

    public RedisSessionManger(RedisSessionClient sessionClient){
        this.sessionClient = sessionClient;
        cleanExpireSession();
    }

    /**
     *  1. cookie JSESSIONID 中load sessionId
     *  2. 若sessionId存在，redis load session
     *  3. 若不存在，创建新session
     */
    public RedisSession createSession(HttpServletRequestWrapper request, HttpServletResponse response, RequestEventSubject requestEventSubject, boolean create) {
        String sessionId = getRequestedSessionId(request);
        RedisSession session = null;
        if (StringUtils.isEmpty(sessionId) && !create){
            return null;
        }
        if (!StringUtils.isEmpty(sessionId)) {
            session = loadSession(sessionId);
        }
        if (session == null && create) {
            session = createNewSession(request, response);
        }
        if (session != null){
            attachEvent(session, request, response, requestEventSubject);
        }

        return session;
    }


    private String getRequestedSessionId(HttpServletRequestWrapper request){
        Cookie[] cookies = request.getCookies();
        if(cookies == null || cookies.length==0) {
            return null;
        }
        String sessionId = null;
        for(Cookie cookie: cookies){
            if(SESSION_ID_COOKIE.equals(cookie.getName())){
                sessionId = cookie.getValue();
            }
        }
        return sessionId;
    }

    private void  saveSession(RedisSession session) {
        try {
            if(session.getExpired()) {
                sessionClient.delete(session.getId());
            } else {
                Long time = getExpireTime(session.getMaxInactiveInterval() + expirationUpdateInterval);
                sessionClient.set(time, session);
            }
        } catch (Exception e) {
            throw new RedisSessionException(e);
        }
    }

    private RedisSession createNewSession(HttpServletRequestWrapper request, HttpServletResponse response) {
        RedisSession session = new RedisSession(sessionClient.createSessionId());
        session.setMaxInactiveInterval(maxInactiveInterval);
        saveCookie(session, request, response);
        return session;
    }

    /**
     * 1. save session
     * 2. write cookie
     */
    private void attachEvent(final RedisSession session, final HttpServletRequestWrapper request, final HttpServletResponse response, RequestEventSubject requestEventSubject) {
       session.setListener(new SessionListenerAdaptor(){
           @Override
           public void onAttributeChanged(HttpSession session) {
                saveSession((RedisSession)session);
                saveCookie((RedisSession)session,request,response);
           }

           @Override
           public void onInvalidated(HttpSession session) {
               removeSession(session.getId());
           }
       });
        requestEventSubject.register((servletRequest,servletResponse)-> {
            int updateInterval = (int) ((System.currentTimeMillis() - session.getLastAccessedTime()) / 1000);
            //非新创建session，数据未更改且未到更新间隔，则不更新redis
            if (!session.getNew()  && !session.getDirty()  && updateInterval < expirationUpdateInterval){
                return;
            }
            if (session.getNew() && session.getExpired()){
                return;
            }
            session.setLastAccessedTime(System.currentTimeMillis());
            saveSession(session);
        });
        session.setRequest(request);
    }

    /**
     * save session
     */
    private void saveCookie(RedisSession session, HttpServletRequestWrapper request, HttpServletResponse response) {
        if (!session.getNew() && !session.getExpired()) {
            return;
        }
        Cookie cookie = new Cookie(SESSION_ID_COOKIE, null);
        cookie.setPath(request.getContextPath());
        if(session.getExpired()){
            cookie.setMaxAge(0);
        }else if (session.getNew()){
            cookie.setValue(session.getId());
        }
        response.addCookie(cookie);
    }

    /**
     *  load session
     */
    private RedisSession loadSession(String sessionId) {
        try {
            RedisSession session = sessionClient.get(sessionId);
            if(session != null){
                session.setNew(false);
                session.setDirty(false);
            }
            return session;
        } catch (Exception e) {
           e.printStackTrace();
        }
        return null;
    }


    /**
     * delete session
     */
    private void removeSession(String sessionId){
        sessionClient.delete(sessionId);
    }


    /**
     * 线程池，用来定期删除redis 中过期的session
     */
    private   void cleanExpireSession() {
        new Thread(()->{
            while (true) {
                System.out.println("clean redis session start time :" + System.currentTimeMillis());
                try {
                    Set<String> sessionIds = sessionClient.rangeSessionSet(0L,getExpireTimeZone());
                    if (sessionIds != null && !sessionIds.isEmpty()){
                        for (String sessionId:sessionIds) {
                            sessionClient.delete(sessionId);
                        }
                    }
                    Thread.sleep(expirationUpdateInterval * 1000);
                } catch (Exception e) {
                    System.out.println("redis-session-task 定期更新过期session 失败!");
                    e.printStackTrace();
                }
            }
        }).start();
    }

    /**
     *  redis
     */
    private static Long getExpireTime (Integer time){
        return System.currentTimeMillis()/1000 + time;
    }


    private static Long getExpireTimeZone(){
        return System.currentTimeMillis()/1000 - (expirationUpdateInterval + maxInactiveInterval) ;
    }

}
